// tge.rs
// copyright zipxing@hotmail.com 2022~2023

//! 主要实现了Adapter适配器，另外，
//! sdl相关处理，统一在这里实现
//! 包含宽度，整体缩放，初始化设置
//! 绘制helper，绘制logo，边框等
//! 初始化部分代码在context.rs中调用
//! 绘制部分代码在terminal.rs中调用
//! cell.rs中也调用了部分代码
#[cfg(feature = "sdl")]
use crate::render::{sprite::Sprite, terminal::Term};
#[cfg(feature = "sdl")]
use crate::{
    render::{adapter::Adapter, buffer::Buffer, sprite::Sprites, style::Color},
    util::{
        get_abs_path,
        objpool::{GObj, GameObjPool, GameObject},
        Rand, Rect,
    },
    LOGO_FRAME,
};
#[cfg(feature = "sdl")]
use crossterm::event::{
    Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use lazy_static::lazy_static;
#[cfg(feature = "sdl")]
use sdl2::{
    event::Event as SEvent,
    image::{InitFlag, LoadSurface, LoadTexture},
    keyboard::Keycode as SKeycode,
    mouse::*,
    pixels::PixelFormatEnum,
    rect::{Point as SPoint, Rect as SRect},
    render::{Canvas, Texture},
    surface::Surface,
    video::{Window, WindowPos::Positioned},
    EventPump, Sdl,
};
use std::collections::HashMap;
#[cfg(feature = "sdl")]
use std::time::Duration;

pub const SDL_LOGO_WIDTH: usize = 33;
pub const SDL_LOGO_HEIGHT: usize = 6;
pub const SDL_LOGO: [u8; SDL_LOGO_WIDTH * SDL_LOGO_HEIGHT * 3] = [
    95, 51, 1, 126, 51, 1, 160, 51, 1, 92, 6, 1, 160, 87, 1, 105, 87, 1, 233, 204, 1, 160, 204, 1,
    223, 204, 1, 233, 204, 1, 160, 204, 1, 223, 204, 1, 32, 0, 0, 95, 220, 1, 160, 220, 1, 105,
    220, 1, 233, 221, 1, 223, 220, 1, 92, 100, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0,
    32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32,
    0, 0, 32, 15, 1, 160, 51, 1, 92, 31, 1, 32, 0, 0, 32, 0, 0, 160, 204, 1, 92, 88, 1, 32, 0, 0,
    32, 0, 0, 32, 0, 0, 95, 204, 1, 92, 88, 1, 160, 220, 1, 92, 100, 1, 32, 0, 0, 32, 0, 0, 32, 0,
    0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0,
    32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 160, 51, 1, 92,
    31, 1, 32, 0, 0, 32, 0, 0, 95, 204, 1, 92, 88, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 15, 1, 32,
    0, 0, 160, 220, 1, 98, 220, 1, 98, 220, 1, 98, 220, 1, 92, 100, 1, 32, 0, 0, 32, 0, 0, 32, 0,
    0, 32, 0, 0, 32, 0, 0, 160, 125, 1, 92, 52, 1, 233, 125, 1, 105, 125, 1, 92, 52, 1, 233, 76, 1,
    160, 76, 1, 160, 76, 1, 105, 76, 1, 92, 2, 1, 32, 0, 0, 32, 0, 0, 160, 51, 1, 92, 31, 1, 32, 0,
    0, 32, 0, 0, 233, 204, 1, 92, 88, 1, 83, 204, 1, 226, 204, 1, 226, 204, 1, 223, 204, 1, 32, 0,
    0, 160, 220, 1, 92, 100, 1, 104, 3, 1, 104, 100, 1, 104, 100, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0,
    32, 0, 0, 32, 0, 0, 160, 125, 1, 233, 125, 1, 105, 125, 1, 32, 15, 1, 32, 15, 1, 95, 76, 1, 92,
    22, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 160, 51, 1, 92, 31, 1, 32, 0, 0, 32,
    0, 0, 160, 204, 1, 92, 88, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 95, 204, 1, 92, 88, 1, 160, 220, 1,
    92, 100, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0, 32, 0, 0,
    160, 125, 1, 92, 52, 1, 32, 0, 0, 32, 0, 0, 32, 15, 1, 233, 76, 1, 95, 76, 1, 160, 76, 1, 223,
    76, 1, 32, 0, 0, 32, 0, 0, 32, 0, 0, 95, 51, 1, 92, 31, 1, 32, 0, 0, 32, 0, 0, 95, 204, 1, 160,
    204, 1, 105, 204, 1, 95, 204, 1, 160, 204, 1, 105, 204, 1, 92, 88, 1, 95, 220, 1, 160, 220, 1,
    160, 220, 1, 160, 220, 1, 223, 220, 1, 92, 100, 1, 32, 0, 0, 81, 41, 1, 92, 22, 1, 32, 0, 0,
    160, 125, 1, 92, 52, 1, 32, 0, 0, 32, 15, 1, 32, 15, 1, 95, 76, 1, 160, 76, 1, 102, 22, 1, 105,
    76, 1, 32, 0, 0,
];

// 一个SDL符号的像素宽和高
#[cfg(feature = "sdl")]
pub const SDL_SYM_WIDTH: f32 = 16.0;
#[cfg(feature = "sdl")]
pub const SDL_SYM_HEIGHT: f32 = 16.0;

// 需要扩展时，在这个列表中添加路径即可
// 最多支持255个texture文件
#[cfg(feature = "sdl")]
pub const SDL_TEXTURE_FILES: [&'static str; 4] = [
    "assets/sdl/c64l.png",
    "assets/sdl/c64u.png",
    "assets/sdl/c64e1.png",
    "assets/sdl/c64e2.png",
];

/// 按照索引返回sdlsym字符
/// 用256个unicode数学符号标识sym在一个sdl纹理中的序号
/// unicode: 0x2200 ~ 0x22FF
/// 对应utf8三字节模式: 11100010 100010xx 10xxxxxx
/// 从utf8字节序列中取出8位idx，标识纹理中的偏移量
pub fn sdlsym(idx: u8) -> &'static str {
    const SSYM: [&str; 256] = [
        "∀", "∁", "∂", "∃", "∄", "∅", "∆", "∇", "∈", "∉", "∊", "∋", "∌", "∍", "∎", "∏", "∐", "∑",
        "−", "∓", "∔", "∕", "∖", "∗", "∘", "∙", "√", "∛", "∜", "∝", "∞", "∟", "∠", "∡", "∢", "∣",
        "∤", "∥", "∦", "∧", "∨", "∩", "∪", "∫", "∬", "∭", "∮", "∯", "∰", "∱", "∲", "∳", "∴", "∵",
        "∶", "∷", "∸", "∹", "∺", "∻", "∼", "∽", "∾", "∿", "≀", "≁", "≂", "≃", "≄", "≅", "≆", "≇",
        "≈", "≉", "≊", "≋", "≌", "≍", "≎", "≏", "≐", "≑", "≒", "≓", "≔", "≕", "≖", "≗", "≘", "≙",
        "≚", "≛", "≜", "≝", "≞", "≟", "≠", "≡", "≢", "≣", "≤", "≥", "≦", "≧", "≨", "≩", "≪", "≫",
        "≬", "≭", "≮", "≯", "≰", "≱", "≲", "≳", "≴", "≵", "≶", "≷", "≸", "≹", "≺", "≻", "≼", "≽",
        "≾", "≿", "⊀", "⊁", "⊂", "⊃", "⊄", "⊅", "⊆", "⊇", "⊈", "⊉", "⊊", "⊋", "⊌", "⊍", "⊎", "⊏",
        "⊐", "⊑", "⊒", "⊓", "⊔", "⊕", "⊖", "⊗", "⊘", "⊙", "⊚", "⊛", "⊜", "⊝", "⊞", "⊟", "⊠", "⊡",
        "⊢", "⊣", "⊤", "⊥", "⊦", "⊧", "⊨", "⊩", "⊪", "⊫", "⊬", "⊭", "⊮", "⊯", "⊰", "⊱", "⊲", "⊳",
        "⊴", "⊵", "⊶", "⊷", "⊸", "⊹", "⊺", "⊻", "⊼", "⊽", "⊾", "⊿", "⋀", "⋁", "⋂", "⋃", "⋄", "⋅",
        "⋆", "⋇", "⋈", "⋉", "⋊", "⋋", "⋌", "⋍", "⋎", "⋏", "⋐", "⋑", "⋒", "⋓", "⋔", "⋕", "⋖", "⋗",
        "⋘", "⋙", "⋚", "⋛", "⋜", "⋝", "⋞", "⋟", "⋠", "⋡", "⋢", "⋣", "⋤", "⋥", "⋦", "⋧", "⋨", "⋩",
        "⋪", "⋫", "⋬", "⋭", "⋮", "⋯", "⋰", "⋱", "⋲", "⋳", "⋴", "⋵", "⋶", "⋷", "⋸", "⋹", "⋺", "⋻",
        "⋼", "⋽", "⋾", "⋿",
    ];
    SSYM[idx as usize]
}

lazy_static! {
    /// 对于一些常用字符，也可以不采用unicode数学符号
    /// 而是直接查找SDL_SYM_MAP得到字符在assets/c64l.png中的偏移量
    /// SDL_SYM_MAP中预置了大小写字母和一些制表符
    /// 这样就方便了用set_str在SDL模式设置一些英文句子
    /// 参考模块的注释
    pub static ref SDL_SYM_MAP: HashMap<String, u8> = {
        let syms = "@abcdefghijklmnopqrstuvwxyz[£]↑← !\"#$%&'()*+,-./0123456789:;<=>?─ABCDEFGHIJKLMNOPQRSTUVWXYZ┼";
        let mut sm: HashMap<String, u8> = HashMap::from([
            ("▇".to_string(), 209),
            ("▒".to_string(), 94),
            ("∙".to_string(), 122),
            ("│".to_string(), 93),
            ("┐".to_string(), 110),
            ("╮".to_string(), 73),
            ("┌".to_string(), 112),
            ("╭".to_string(), 85),
            ("└".to_string(), 109),
            ("╰".to_string(), 74),
            ("┘".to_string(), 125),
            ("╯".to_string(), 75),
        ]);
        for (i, s) in syms.chars().enumerate() {
            sm.insert(s.to_string(), i as u8);
        }
        sm
    };
}

/// 从symbol string获取索引idx
/// 如果是unicode数学符号，则计算返回idx
/// 否则从SDL_SYM_MAP中查找索引
pub fn sdlinfo(symbol: &String) -> u8 {
    let sbts = symbol.as_bytes();
    if sbts.len() == 3 {
        //判断是unicode数学符号
        if sbts[0] == 0xe2 && (sbts[1] >> 2 == 0x22) {
            let idx = ((sbts[1] & 3) << 6) + (sbts[2] & 0x3f);
            return idx as u8;
        }
    }
    let mut ret = 0u8;
    if let Some(idx) = SDL_SYM_MAP.get(symbol) {
        ret = *idx;
    }
    ret
}

/// sym_index, texture_index, fg_color_index
pub type SdlCellInfo = (u8, u8, u8);

#[cfg(feature = "sdl")]
pub struct SdlAdapter {
    pub title: String,
    // 拖动窗口需要的数据
    pub drag_need: bool,
    pub drag_ing: bool,
    pub drag_mouse_x: i32,
    pub drag_mouse_y: i32,
    pub drag_dx: i32,
    pub drag_dy: i32,
    // 按cell的宽度高度
    pub cell_w: u16,
    pub cell_h: u16,
    // 按pixel的宽度高度
    pub pixel_w: u32,
    pub pixel_h: u32,
    pub ratio_x: f32,
    pub ratio_y: f32,
    // sdl对象
    pub context: Sdl,
    pub event_pump: Option<EventPump>,
    // rust-sdl2中为了定制鼠标形状
    pub cursor: Option<Cursor>,
    pub canvas: Option<Canvas<Window>>,
    // 源材质
    pub asset_textures: Option<Vec<Texture>>,
    // 绘制目标材质
    pub render_texture: Option<Texture>,
    pub rd: Rand,
}

#[cfg(feature = "sdl")]
pub enum SdlBorderArea {
    NOPE,
    CLOSE,
    TOPBAR,
    OTHER,
}

#[cfg(feature = "sdl")]
impl SdlAdapter {
    pub fn new() -> Self {
        Self {
            context: sdl2::init().unwrap(),
            event_pump: None,
            cell_w: 100,
            cell_h: 100,
            pixel_w: 0,
            pixel_h: 0,
            title: "tge.rs".to_string(),
            ratio_x: 1.0,
            ratio_y: 1.0,
            cursor: None,
            canvas: None,
            asset_textures: None,
            render_texture: None,
            drag_ing: false,
            drag_mouse_x: 0,
            drag_mouse_y: 0,
            drag_need: false,
            drag_dx: 0,
            drag_dy: 0,
            rd: Rand::new(),
        }
    }

    fn set_mouse_cursor(&mut self, s: &Surface) {
        self.cursor = Some(
            Cursor::from_surface(s, 0, 0)
                .map_err(|err| format!("failed to load cursor: {}", err))
                .unwrap(),
        );
        match &self.cursor {
            Some(cursor) => {
                cursor.set();
            }
            _ => {}
        }
        self.context.mouse().show_cursor(true);
    }

    fn in_border(&self, x: i32, y: i32) -> SdlBorderArea {
        let w = self.cell_width();
        let h = self.cell_height();
        let sw = self.cell_w + 2;
        if y >= 0 && y < h as i32 {
            if x >= 0 && x <= ((sw - 1) as f32 * w) as i32 {
                return SdlBorderArea::TOPBAR;
            }
            if x > ((sw - 1) as f32 * w) as i32 && x <= (sw as f32 * w) as i32 {
                return SdlBorderArea::CLOSE;
            }
        } else {
            if x > w as i32 && x <= ((sw - 1) as f32 * w) as i32 {
                return SdlBorderArea::NOPE;
            }
        }
        SdlBorderArea::OTHER
    }

    fn drag_window(&mut self, event: &SEvent) -> bool {
        match *event {
            SEvent::Quit { .. }
            | SEvent::KeyDown {
                keycode: Some(SKeycode::Q),
                ..
            } => return true,

            SEvent::MouseButtonDown {
                mouse_btn: sdl2::mouse::MouseButton::Left,
                x,
                y,
                ..
            } => {
                let bs = self.in_border(x, y);
                match bs {
                    SdlBorderArea::TOPBAR | SdlBorderArea::OTHER => {
                        // 当鼠标左键按下时，开始拖动
                        self.drag_ing = true;
                        self.drag_mouse_x = x;
                        self.drag_mouse_y = y;
                    }
                    SdlBorderArea::CLOSE => {
                        return true;
                    }
                    _ => {}
                }
            }
            SEvent::MouseButtonUp {
                mouse_btn: sdl2::mouse::MouseButton::Left,
                ..
            } => {
                // 当鼠标左键释放时，停止拖动
                self.drag_ing = false;
            }
            SEvent::MouseMotion { x, y, .. } if self.drag_ing => {
                self.drag_need = true;
                // 如果鼠标在移动并且左键处于按下状态，那么就移动窗口
                self.drag_dx = x - self.drag_mouse_x;
                self.drag_dy = y - self.drag_mouse_y;
            }
            _ => {}
        }
        false
    }

    pub fn set_ratiox(&mut self, rx: f32) -> &mut SdlAdapter {
        self.ratio_x = rx;
        self
    }

    pub fn set_ratioy(&mut self, ry: f32) -> &mut SdlAdapter {
        self.ratio_y = ry;
        self
    }

    pub fn set_size(&mut self, w: u16, h: u16) -> &mut SdlAdapter {
        self.cell_w = w;
        self.cell_h = h;
        self
    }

    pub fn set_pixel_size(&mut self) -> &mut SdlAdapter {
        self.pixel_w = ((self.cell_w + 2) as f32 * SDL_SYM_WIDTH / self.ratio_x) as u32;
        self.pixel_h = ((self.cell_h + 2) as f32 * SDL_SYM_HEIGHT / self.ratio_y) as u32;
        self
    }

    pub fn set_title(&mut self, s: String) -> &mut SdlAdapter {
        self.title = s;
        self
    }

    /// 赋予动态更新sym点阵数据的能力
    /// pdat数据长度应该是16 * 16 * 4(RGBA)
    pub fn update_cell_texture(&mut self, tex_idx: u8, sym_idx: u8, pdat: &[u8]) {
        match &mut self.asset_textures {
            Some(st) => {
                let w = SDL_SYM_WIDTH as i32;
                let h = SDL_SYM_HEIGHT as i32;
                let srcx = sym_idx as i32 % w;
                let srcy = sym_idx as i32 / w;
                let sr = SRect::new((w + 1) * srcx, (h + 1) * srcy, w as u32, h as u32);
                st[tex_idx as usize]
                    .update(sr, pdat, 4 * SDL_SYM_WIDTH as usize)
                    .unwrap();
            }
            _ => {}
        }
    }
}

#[cfg(feature = "sdl")]
impl Adapter for SdlAdapter {
    fn init(&mut self, w: u16, h: u16, rx: f32, ry: f32, s: String) {
        self.set_size(w, h)
            .set_ratiox(rx)
            .set_ratioy(ry)
            .set_pixel_size()
            .set_title(s);

        let video_subsystem = self.context.video().unwrap();
        let _image_context = sdl2::image::init(InitFlag::PNG | InitFlag::JPG).unwrap();

        let window = video_subsystem
            .window(&self.title, self.pixel_w, self.pixel_h)
            .position_centered()
            .borderless()
            // .fullscreen()
            .build()
            .map_err(|e| e.to_string())
            .unwrap();

        let canvas = window
            .into_canvas()
            .software()
            .build()
            .map_err(|e| e.to_string())
            .unwrap();

        let texture_creator = canvas.texture_creator();
        let mut vt: Vec<Texture> = vec![];
        for t in 0..SDL_TEXTURE_FILES.len() {
            vt.push(texture_creator.load_texture(SDL_TEXTURE_FILES[t]).unwrap());
        }
        let rt = texture_creator
            .create_texture_target(PixelFormatEnum::RGBA8888, self.pixel_w, self.pixel_h)
            .unwrap();

        let surface = Surface::from_file(get_abs_path("assets/sdl/cursor.png"))
            .map_err(|err| format!("failed to load cursor image: {}", err))
            .unwrap();
        self.set_mouse_cursor(&surface);

        self.canvas = Some(canvas);
        self.asset_textures = Some(vt);
        self.render_texture = Some(rt);
        self.event_pump = Some(self.context.event_pump().unwrap());
    }

    fn reset(&mut self) {}

    fn cell_width(&self) -> f32 {
        SDL_SYM_WIDTH / self.ratio_x
    }

    fn cell_height(&self) -> f32 {
        SDL_SYM_HEIGHT / self.ratio_y
    }

    fn size(&self) -> Rect {
        Rect::new(0, 0, self.cell_w, self.cell_h)
    }

    fn poll_event(&mut self, timeout: Duration, es: &mut Vec<Event>) -> bool {
        let mut ses: Vec<SEvent> = vec![];
        match &mut self.event_pump {
            Some(ref mut ep) => {
                for event in ep.poll_iter() {
                    ses.push(event.clone());
                    // 转换sdl的事件为crossterm输入事件，方便游戏编码采用统一接口处理
                    if let Some(et) = input_events_sdl2crossterm(&event, self.ratio_x, self.ratio_y)
                    {
                        if !self.drag_ing {
                            es.push(et);
                        }
                    }
                }
                for event in ses {
                    // sdl窗口是borderless的，自己绘制的窗口标题栏和
                    // 边框，处理边框拖动，关闭等鼠标事件
                    if self.drag_window(&event) {
                        return true;
                    }
                }
                ::std::thread::sleep(timeout);
            }
            _ => {}
        }
        false
    }

    fn render_buffer(
        &mut self,
        current_buffer: &Buffer,
        _p: &Buffer,
        pixel_sprites: &mut Sprites,
        stage: u32,
    ) -> Result<(), String> {
        let width = current_buffer.area.width;

        if stage <= LOGO_FRAME {
            render_logo(self, stage);
            return Ok(());
        }
        if let (Some(c), Some(rt), Some(texs)) = (
            &mut self.canvas,
            &mut self.render_texture,
            &mut self.asset_textures,
        ) {
            // 拖动窗口，设置窗口正确位置
            sdl_move_win(&mut self.drag_need, c, self.drag_dx, self.drag_dy);
            c.clear();
            c.with_texture_canvas(rt, |tc| {
                tc.clear();
                let rx = self.ratio_x;
                let ry = self.ratio_y;
                render_border(self.cell_w, self.cell_h, rx, ry, texs, tc);
                render_main_buffer(current_buffer, width, rx, ry, texs, tc);
                render_pixel_sprites(pixel_sprites, rx, ry, texs, tc);
            })
            .unwrap();
            c.copy(rt, None, None).unwrap();
            c.present();
        }
        Ok(())
    }

    fn hide_cursor(&mut self) -> Result<(), String> {
        Ok(())
    }

    fn show_cursor(&mut self) -> Result<(), String> {
        Ok(())
    }

    fn set_cursor(&mut self, _x: u16, _y: u16) -> Result<(), String> {
        Ok(())
    }

    fn get_cursor(&mut self) -> Result<(u16, u16), String> {
        Ok((0, 0))
    }
}

#[cfg(feature = "sdl")]
pub fn sdl_move_win(drag_need: &mut bool, c: &mut Canvas<Window>, dx: i32, dy: i32) {
    // 拖动窗口，设置窗口正确位置
    if *drag_need {
        let win = c.window_mut();
        let (win_x, win_y) = win.position();
        win.set_position(Positioned(win_x + dx), Positioned(win_y + dy));
        *drag_need = false;
    }
}

#[cfg(feature = "sdl")]
pub fn render_sdl_helper(
    cell_w: u16,
    rx: f32,
    ry: f32,
    i: usize,
    sh: &(u8, u8, u8),
    px: u16,
    py: u16,
    is_border: bool,
) -> (SRect, SRect, usize) {
    let w = SDL_SYM_WIDTH as i32;
    let h = SDL_SYM_HEIGHT as i32;
    let dstx = i as u16 % cell_w;
    let dsty = i as u16 / cell_w;
    let srcx = sh.0 as i32 % w;
    let srcy = sh.0 as i32 / w;
    let tex_count = SDL_TEXTURE_FILES.len() as u8;
    let tx = if sh.1 < tex_count { sh.1 as usize } else { 1 };
    (
        SRect::new((w + 1) * srcx, (h + 1) * srcy, w as u32, h as u32),
        SRect::new(
            (dstx + if is_border { 0 } else { 1 }) as i32 * (w as f32 / rx) as i32 + px as i32,
            (dsty + if is_border { 0 } else { 1 }) as i32 * (h as f32 / ry) as i32 + py as i32,
            (w as f32 / rx) as u32,
            (h as f32 / ry) as u32,
        ),
        tx,
    )
}

#[cfg(feature = "sdl")]
pub fn render_pixel_sprites(
    pixel_spt: &mut Sprites,
    rx: f32,
    ry: f32,
    texs: &mut Vec<Texture>,
    tc: &mut Canvas<Window>,
) {
    // 渲染按像素移动的精灵
    // 按照render_weight进行排序, render_weight越大的越后面渲染（上层)
    pixel_spt.update_render_index();
    for si in &pixel_spt.render_index {
        let s = &pixel_spt.sprites[si.0];
        if s.is_hidden() {
            continue;
        }
        let px = s.content.area.x;
        let py = s.content.area.y;
        let pw = s.content.area.width;
        let ph = s.content.area.height;

        for (i, cell) in s.content.content.iter().enumerate() {
            let sh = &cell.get_sdl_info();
            let (s1, s2, texidx) = render_sdl_helper(pw, rx, ry, i, sh, px, py, false);
            let x = i % pw as usize;
            let y = i / pw as usize;
            // 计算中心点相对坐标
            let ccp = SPoint::new(
                ((pw as f32 / 2.0 - x as f32) * SDL_SYM_WIDTH as f32 / rx) as i32,
                ((ph as f32 / 2.0 - y as f32) * SDL_SYM_HEIGHT as f32 / ry) as i32,
            );
            let tx = &mut texs[texidx];
            let fc = Color::Indexed(sh.2).get_rgb();
            tx.set_color_mod(fc.0, fc.1, fc.2);
            tc.copy_ex(tx, s1, s2, s.angle, ccp, false, false).unwrap();
        }
    }
}

#[cfg(feature = "sdl")]
pub fn render_main_buffer(
    buf: &Buffer,
    width: u16,
    rx: f32,
    ry: f32,
    texs: &mut Vec<Texture>,
    tc: &mut Canvas<Window>,
) {
    // 渲染主buffer
    for (i, cell) in buf.content.iter().enumerate() {
        // 把sdl_history上积累的sym，依次渲染
        // 从而支持重叠透明像素
        for sh in &cell.sdl_history {
            let (s1, s2, texidx) = render_sdl_helper(width, rx, ry, i, sh, 0, 0, false);
            let tx = &mut texs[texidx];
            let fc = Color::Indexed(sh.2).get_rgb();
            tx.set_color_mod(fc.0, fc.1, fc.2);
            tc.copy(tx, s1, s2).unwrap();
        }
    }
}

#[cfg(feature = "sdl")]
pub fn render_border(
    cell_w: u16,
    cell_h: u16,
    rx: f32,
    ry: f32,
    vt: &mut Vec<Texture>,
    canvas: &mut Canvas<Window>,
) {
    let sh_top = (102u8, 1u8, 7u8);
    let sh_other = (24u8, 2u8, 7u8);
    let sh_close = (214u8, 1u8, 7u8);

    for n in 0..cell_h as usize + 2 {
        for m in 0..cell_w as usize + 2 {
            if n != 0 && n != cell_h as usize + 1 && m != 0 && m != cell_w as usize + 1 {
                continue;
            }
            let rsh;
            if n == 0 {
                if m as u16 <= cell_w {
                    rsh = &sh_top;
                } else {
                    rsh = &sh_close;
                }
            } else {
                rsh = &sh_other;
            }
            let (s1, s2, texidx) = render_sdl_helper(
                cell_w + 2,
                rx,
                ry,
                n * (cell_w as usize + 2) + m,
                rsh,
                0,
                0,
                true,
            );
            let tx = &mut vt[texidx];
            let fc = Color::Indexed(rsh.2).get_rgb();
            tx.set_color_mod(fc.0, fc.1, fc.2);
            canvas.copy(tx, s1, s2).unwrap();
        }
    }
}

#[cfg(feature = "sdl")]
pub fn render_logo(sc: &mut SdlAdapter, stage: u32) {
    let rx = sc.ratio_x * 1.2;
    let ry = sc.ratio_y * 1.2;
    if let (Some(canvas), Some(texs)) = (&mut sc.canvas, &mut sc.asset_textures) {
        canvas.clear();
        render_border(sc.cell_w, sc.cell_h, sc.ratio_x, sc.ratio_y, texs, canvas);
        for y in 0usize..SDL_LOGO_HEIGHT {
            let randadj = 8 - (sc.rd.rand() % 16) as i32;
            for x in 0usize..SDL_LOGO_WIDTH {
                let sci = y * SDL_LOGO_WIDTH + x;
                let symw = SDL_SYM_WIDTH / rx;
                let symh = SDL_SYM_HEIGHT / ry;

                let (s1, mut s2, texidx) = render_sdl_helper(
                    SDL_LOGO_WIDTH as u16,
                    rx,
                    ry,
                    sci,
                    &(
                        SDL_LOGO[sci * 3],
                        SDL_LOGO[sci * 3 + 2],
                        SDL_LOGO[sci * 3 + 1],
                    ),
                    sc.pixel_w as u16 / 2 - (SDL_LOGO_WIDTH as f32 / 2.0 * symw) as u16,
                    sc.pixel_h as u16 / 2 - (SDL_LOGO_HEIGHT as f32 / 2.0 * symh) as u16,
                    false,
                );
                let tx = &mut texs[texidx];
                let sg = LOGO_FRAME as u8 / 3;
                let fc = Color::Indexed(SDL_LOGO[sci * 3 + 1]).get_rgb();

                if stage <= sg as u32 {
                    tx.set_color_mod(
                        (stage as u8).saturating_mul(10),
                        (stage as u8).saturating_mul(10),
                        (stage as u8).saturating_mul(10),
                    );
                    s2.set_x(s2.x() + randadj);
                } else if stage <= sg as u32 * 2 {
                    tx.set_color_mod(fc.0, fc.1, fc.2);
                } else {
                    let cc = (stage as u8 - sg as u8 * 2).saturating_mul(10);
                    tx.set_color_mod(
                        fc.0.saturating_sub(cc),
                        fc.1.saturating_sub(cc),
                        fc.2.saturating_sub(cc),
                    );
                }
                canvas.copy(tx, s1, s2).unwrap();
            }
        }
        canvas.present();
    }
}

/// 转换sdl的输入事件为crossterm输入事件，方便游戏采用统一接口处理
/// 键盘和鼠标事件的处理，请参考game/unblock/model.rs的handle_input方法
#[cfg(feature = "sdl")]
pub fn input_events_sdl2crossterm(e: &SEvent, adjx: f32, adjy: f32) -> Option<Event> {
    let sym_width = SDL_SYM_WIDTH as f32;
    let sym_height = SDL_SYM_HEIGHT as f32;
    let mut mcte: Option<MouseEvent> = None;
    match e {
        SEvent::KeyDown { keycode, .. } => {
            let kc;
            match keycode {
                Some(SKeycode::Space) => kc = ' ',
                Some(SKeycode::A) => kc = 'a',
                Some(SKeycode::B) => kc = 'b',
                Some(SKeycode::C) => kc = 'c',
                Some(SKeycode::D) => kc = 'd',
                Some(SKeycode::E) => kc = 'e',
                Some(SKeycode::F) => kc = 'f',
                Some(SKeycode::G) => kc = 'g',
                Some(SKeycode::H) => kc = 'h',
                Some(SKeycode::I) => kc = 'i',
                Some(SKeycode::J) => kc = 'j',
                Some(SKeycode::K) => kc = 'k',
                Some(SKeycode::L) => kc = 'l',
                Some(SKeycode::M) => kc = 'm',
                Some(SKeycode::N) => kc = 'n',
                Some(SKeycode::O) => kc = 'o',
                Some(SKeycode::P) => kc = 'p',
                Some(SKeycode::Q) => kc = 'q',
                Some(SKeycode::R) => kc = 'r',
                Some(SKeycode::S) => kc = 's',
                Some(SKeycode::T) => kc = 't',
                Some(SKeycode::U) => kc = 'u',
                Some(SKeycode::V) => kc = 'v',
                Some(SKeycode::W) => kc = 'w',
                Some(SKeycode::X) => kc = 'x',
                Some(SKeycode::Y) => kc = 'y',
                Some(SKeycode::Z) => kc = 'z',
                _ => {
                    return None;
                }
            }
            let cte = KeyEvent::new(KeyCode::Char(kc), KeyModifiers::NONE);
            return Some(Event::Key(cte));
        }
        SEvent::MouseButtonUp { x, y, .. } => {
            let cte = MouseEvent {
                kind: MouseEventKind::Up(MouseButton::Left),
                column: *x as u16,
                row: *y as u16,
                modifiers: KeyModifiers::NONE,
            };
            mcte = Some(cte);
        }
        SEvent::MouseButtonDown { x, y, .. } => {
            let cte = MouseEvent {
                kind: MouseEventKind::Down(MouseButton::Left),
                column: *x as u16,
                row: *y as u16,
                modifiers: KeyModifiers::NONE,
            };
            mcte = Some(cte);
        }
        SEvent::MouseMotion {
            x, y, mousestate, ..
        } => {
            if mousestate.left() {
                let cte = MouseEvent {
                    kind: MouseEventKind::Drag(MouseButton::Left),
                    column: *x as u16,
                    row: *y as u16,
                    modifiers: KeyModifiers::NONE,
                };
                mcte = Some(cte);
            } else {
                let cte = MouseEvent {
                    kind: MouseEventKind::Moved,
                    column: *x as u16,
                    row: *y as u16,
                    modifiers: KeyModifiers::NONE,
                };
                mcte = Some(cte);
            }
        }
        _ => {}
    }
    if let Some(mut mc) = mcte {
        mc.column /= (sym_width / adjx) as u16;
        mc.row /= (sym_height / adjy) as u16;
        if mc.column >= 1 {
            mc.column -= 1;
        }
        if mc.row >= 1 {
            mc.row -= 1;
        }
        return Some(Event::Mouse(mc));
    }
    None
}

/// 创建max个精灵
/// 并执行f闭包进行初始化
#[cfg(feature = "sdl")]
pub fn create_sprites<F>(max: usize, pre: &str, size_x: u16, size_y: u16, term: &mut Term, f: F)
where
    F: Fn(&mut Sprite),
{
    for i in 0..max {
        let mut bl = Sprite::new(0, 0, size_x, size_y);
        f(&mut bl);
        bl.set_hidden(true);
        term.add_pixel_sprite(bl, &format!("{}{}", pre, i));
    }
}

/// 绘制精灵
/// 并调用f闭包设置位置和内容
#[cfg(feature = "sdl")]
pub fn draw_objs<T, F>(os: &mut GameObjPool<T>, max: usize, pre: &str, term: &mut Term, f: F)
where
    T: GObj,
    F: Fn(&mut Sprite, &GameObject<T>),
{
    for o in &os.pool {
        // 清理不活跃对象
        if !o.active {
            match os.map.remove(&o.id) {
                Some(oid) => {
                    //info!("render set hidden true...");
                    term.get_pixel_sprite(&format!("{}{}", pre, oid))
                        .set_hidden(true);
                }
                _ => {}
            }
            continue;
        }
        let psid = match os.map.get(&o.id) {
            // 如果对象在map中，设置psid
            Some(oid) => *oid,
            _ => {
                let mut mi = 0;
                // 查找一个可用的精灵
                for i in 0..max {
                    let pp = term.get_pixel_sprite(&format!("{}{}", pre, i));
                    if pp.is_hidden() {
                        mi = i;
                        break;
                    }
                }
                // key是GameObject的id，value是精灵id
                os.map.insert(o.id, mi);
                mi
            }
        };
        // 用pre和psid拼合，获取精灵，设置可见并绘制
        let pl = term.get_pixel_sprite(&format!("{}{}", pre, psid));
        pl.set_hidden(false);
        f(pl, o);
    }
}
